/*
 *
 * Copyright (C) 2012 Walter Lütgenau
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 */
package offset.nodes.client.chooser.model;

import java.util.Collection;
import java.util.Iterator;
import java.util.StringTokenizer;
import offset.nodes.Constants;
import offset.nodes.client.chooser.view.LazyExpandingNode;
import offset.nodes.client.model.Server;

/**
 *
 * @author Walter Lütgenau
 */
public class NodeChooserModel {
    Server server = null;
    
    protected class ServerPopulator implements FolderPopulator {
        Server server;
        String filter;
        
        public ServerPopulator(Server server, String filter) {
            this.server = server;
            this.filter = filter;
        }
        
        public Collection populate(String path) throws Exception {
            FindNodes.Request request = new FindNodes.Request();
            request.setRoot(path+Constants.PATH_SEPARATOR);
            request.setFilter(filter);
            
            return ((FindNodes.Response)server.sendRequest(request)).getResultNodes();
        }
    }
    
    
    /** Creates a new instance of NodeChooserModel */
    public NodeChooserModel(Server server) {
        this.server = server;
    }
    
    public FolderPopulator getPopulator(String filter) {
        return new ServerPopulator(server, filter);
    }
    
    protected LazyExpandingNode addNode( LazyExpandingNode root, FolderNode node, String filter) throws Exception {
        LazyExpandingNode current = root;
        
        String path = node.getPath();
        StringBuffer currentPath = new StringBuffer();
        StringTokenizer parser = new StringTokenizer(path, Constants.PATH_SEPARATOR, true);
        
        while( parser.hasMoreTokens()) {
            String name = parser.nextToken();
            currentPath.append(name);
            if ( name.equals("/"))
                continue;
            
            boolean hasChildren = parser.hasMoreTokens()  || node.hasChildren();
            
            if ( current == null) {
                root = new LazyExpandingNode(getPopulator(filter), new FolderNode(Constants.PATH_SEPARATOR, hasChildren, null, null));
                current = new LazyExpandingNode(getPopulator(filter),new FolderNode(currentPath.toString(),hasChildren, null, null));
                root.add(current);
                continue;
            }
            
            boolean exists = false;
            for( int i=0; i<current.getChildCount(); i++) {
                LazyExpandingNode child = (LazyExpandingNode) current.getChildAt(i);
                if ( child.getName().equals(name)) {
                    current = child;
                    exists = true;
                    continue;
                }
            }
            
            if ( exists )
                continue;
            
            LazyExpandingNode newNode = new LazyExpandingNode(getPopulator(filter),new FolderNode(currentPath.toString(),hasChildren, null, null));
            current.add(newNode);
            current = newNode;            
            if ( !parser.hasMoreTokens())
                current.setNode(node); // set the additional information only in the appropriate element
        }
        
        return root;
    }
    
     /**
     * Create the tree after the initial query.
     *
     * There are two variations:
     * - The query is FILTER_ANY, i.e. all nodes will returned on any level. In that case, the path
     *   nodes will be part of the result as well
     * - The query is different from FILTER_ANY. In this case, the path nodes will not be present.
     *
     * @param root the root node. If null, it will be created
     * @param path
     * @return
     * @throws Exception
     */
    public LazyExpandingNode initPath(LazyExpandingNode root, String path) throws Exception {
        return initPath(root, path, FindNodes.FILTER_ANY);
    }

     /**
     * Create the tree after the initial query.
     *
     * There are two variations:
     * - The query is FILTER_ANY, i.e. all nodes will returned on any level. In that case, the path
     *   nodes will be part of the result as well
     * - The query is different from FILTER_ANY. In this case, the path nodes will not be present.
     *
     * @param path
     * @param filter
     * @return
     * @throws Exception
     */
    public LazyExpandingNode initPath(String path, String filter) throws Exception {
        return initPath(null, path, filter);
    }

     /**
     * Create the tree after the initial query.
     *
     * There are two variations:
     * - The query is FILTER_ANY, i.e. all nodes will returned on any level. In that case, the path
     *   nodes will be part of the result as well
     * - The query is different from FILTER_ANY. In this case, the path nodes will not be present.
     *
     * @param root the root node. If null, it will be created.
     * @param path
     * @param filter the filter. If different from null or FindNodes.FILTER_ANY, the root will be created in any case.
     * @return
     * @throws Exception
     */
    public LazyExpandingNode initPath(LazyExpandingNode root, String path, String filter) throws Exception {
        FindNodes.Request request = new FindNodes.Request();
        request.setRoot(path);
        request.setFilter(filter);
        request.setIncludePathNodes(true);

        FindNodes.Response response = (FindNodes.Response) server.sendRequest(request);
        Collection resultNodes = response.getResultNodes();

        if (filter == null || filter.equals(FindNodes.FILTER_ANY))
            root = addTreeNodes(root, resultNodes, path);
        else
            root = addFilteredTreeNodes(resultNodes, filter);

        return root;
    }

    /**
     * Create a node tree from the query result. Each node may have a completely different path
     * 
     * @param resultNodes
     * @param filter
     * @return
     * @throws Exception
     */
    protected LazyExpandingNode addFilteredTreeNodes(Collection resultNodes, String filter) throws Exception {
        Iterator i = resultNodes.iterator();

        LazyExpandingNode root = null;

        while( i.hasNext()) {
            FolderNode node = (FolderNode) i.next();
            root = addNode( root, node, filter);
        }
        
        return root;
    }

    /**
     * Create a node tree from the query result. The result will first contain the nodes of the path, then the children
     * of the bottom most node.
     * 
     * @param resultNodes
     * @param path
     * @param filter
     * @return
     */
    public LazyExpandingNode addTreeNodes(LazyExpandingNode root, Collection<FolderNode> resultNodes, String path) {
        Iterator<FolderNode> it = resultNodes.iterator();

        while (it.hasNext()) {
            FolderNode folderNode = it.next();
            // do not add children of the selected path. They will be added when expanding the node
            if (path != null && !path.equals(Constants.PATH_SEPARATOR) && isChildPath(path, folderNode.getPath()))
                continue;

            LazyExpandingNode node = new LazyExpandingNode(getPopulator(FindNodes.FILTER_ANY), folderNode);

            if (root == null) {
                root = node;
                continue;
            }

            LazyExpandingNode parent = findParent(root, node);
            if (node.getName().length() > 0 // not root
                    && !hasChild(parent, node)) { // already present
                parent.add(node);
                parent.setPopulated(true);
            }
        }

        return root;
    }

    /**
     * Is the node at path a child of the node at parentPath?
     * 
     * @param parentPath
     * @param path
     * @return
     */
    protected boolean isChildPath(String parentPath, String path) {
        if (!path.startsWith(parentPath))
            return false;
        String childPath = path.substring(parentPath.length());
        if (childPath.indexOf(Constants.PATH_SEPARATOR) < 0)
            return false;
        return true;
    }

    /**
     * Find the parent of the node  in the tree
     *
     * @param parent
     * @param node
     * @return the parent node
     */
    LazyExpandingNode findParent(LazyExpandingNode parent, LazyExpandingNode node) {
        if (parent == null)
            return null;

        String[] names = node.getNode().getPath().split(Constants.PATH_SEPARATOR);

        for (int i = 1; i < names.length - 1; i++) {
            for (int j = 0; j < parent.getChildCount(); j++) {
                if (names[i].equals(((LazyExpandingNode) parent.getChildAt(j)).getNode().getName())) {
                    parent = (LazyExpandingNode) parent.getChildAt(j);
                    break;
                }
            }
        }

        return parent;
    }
    
    /**
     * Is the node already a child of parent?
     * 
     * @param parent the parent node
     * @param node the potential child node
     * @return true or false
     */
    boolean hasChild(LazyExpandingNode parent, LazyExpandingNode node) {
        String name = node.getName();
        for (int j = 0; j < parent.getChildCount(); j++) {
            if (name.equals(((LazyExpandingNode) parent.getChildAt(j)).getNode().getName()))
                return true;
        }
        return false;
    }
    
}
